import 'dart:collection';

import 'package:im_app/im/util/FirstLetterUtil.dart';
import 'package:logger/logger.dart';
import 'package:uuid/uuid.dart';

import '../dao/ImGroupDao.dart';
import '../dao/ImGroupUserDao.dart';
import '../dao/ImSessionDao.dart';
import '../pojo/ImPojo.dart';
import '../socket/dto/base/Resp.dart';
import '../socket/dto/business/PullData.dart';
import '../socket/dto/business/SendData.dart';
import '../socket/service/RpcSendDataService.dart';
import '../util/ImCacheFactory.dart';
import 'BaseService.dart';

class ImGroupService extends BaseService {

  final _logger = Logger(printer: PrettyPrinter(),);

  // final _groupDao = ImGroupDao();
  // final _groupUserDao = ImGroupUserDao();
  // final _sessionDao = ImSessionDao();

  Future<void> saveOrUpdates(String userId, List<GroupInfoItem> groupInfoItemList, bool needCreateSession) async {
    if (groupInfoItemList.isEmpty) return Future.value();

    final groupDao = ImGroupDao();
    final groupUserDao = ImGroupUserDao();
    final sessionDao = ImSessionDao();
    List<ImGroup> imGroups = groupInfoItemList.map((groupInfoItem)=>_toImGroup(userId, groupInfoItem)).toList();

    List<String> codeList = imGroups.map((e) => e.code).toList();
    List<ImGroup> dbGroups = await groupDao.getByCodesAndUserId(codeList, userId);

    Map<String, ImGroup> dbGroupMap = Map.fromIterable(dbGroups, key: (group)=>group.code, value: (group)=>group);
    final insertImGroupSet = Set<ImGroup>();
    final updateGroupSet = Set<ImGroup>();
    final updateGroupUserSet = Set<ImGroupUser>();
    final updateSessionSet = Set<ImSession>();

    for(var imGroup in imGroups) {
      ImGroup? dbGroup = dbGroupMap[imGroup.code];
      if (dbGroup == null) { // 新增群
        insertImGroupSet.add(imGroup);
      } else if (imGroup!.name != null){ // 群变更
        imGroup.id = dbGroup.id;
        if(imGroup.groupUserList != null) {
          imGroup.groupUserList!.forEach((gu)=>gu.groupId = dbGroup.id);
        }
        updateGroupSet.add(imGroup);

        ImSession? imSession = await sessionDao.getByGroupCodeAndUserId(imGroup.code, userId);
        imSession!.name = imGroup.name;
        imSession.updateTime = DateTime.now();
        updateSessionSet.add(imSession);
      } else { // 仅组员信息发生变化
        imGroup.id = dbGroup.id;
        if(imGroup.groupUserList != null) {
          imGroup.groupUserList!.forEach((gu)=>gu.groupId = dbGroup.id);
          updateGroupUserSet.addAll(imGroup.groupUserList!);
        }
      }
    }
    List<ImGroup> insertGroups = insertImGroupSet.toList();
    await groupDao.batchInserts(insertGroups);
    await groupDao.batchUpdates(updateGroupSet.toList());

    List<ImGroupUser> groupUsers = [];
    for (var group in updateGroupSet) {
      if(group.groupUserList != null && group.groupUserList!.isNotEmpty) {
        groupUsers.addAll(group!.groupUserList!.toList());
      }
    }
    groupUsers.addAll(updateGroupUserSet);
    groupUserDao.insertOrUpdates(groupUsers);

    await sessionDao.batchUpdates(userId, updateSessionSet.toList());

    if (needCreateSession) {
      await _createSession(userId, insertGroups);
    }
  }

  Future<void> sendSuccess(ImGroup imGroup, String sessionServerId) async {
    final groupDao = ImGroupDao();
    imGroup.status = ImGroupStatus.enable;
    await groupDao.batchUpdates([imGroup]);
  }

  ImSession buildImGroup(String userId, List<ImUser> imUsers) {
    ImGroup group = ImGroup(code: Uuid().v5(null, null));
    group.groupUserList = imUsers.map((u)=> _toGroupUser(u)).toSet();
    String groupName = _buildGroupName(imUsers);
    group.name = groupName;
    group.code = Uuid().v5(null, null);
    group.groupType = ImGroupType.chat;
    group.status = ImGroupStatus.none;
    group.currentUserId = userId;
    group.ownerId = userId;
    group.firstLetter = FirstLetterUtil.firstLetter(groupName);

    ImSession imSession = _buildSession(userId, group);
    return imSession;
  }

  Set<ImGroupUser> buildImGroupUsers(int groupId, List<ImUser> imUsers) {
    var groupUsers = Set<ImGroupUser>();
    for(var imUser in imUsers) {
      var groupUser = ImGroupUser(imUser.userId);
      groupUser!.user = imUser;
      groupUser.groupId = groupId;
      groupUser.userName = imUser!.userName;
      groupUser.userId = imUser!.userId;
      groupUser.status = ImGroupUserStatus.enable;
      groupUsers.add(groupUser);
    }
    return groupUsers;
  }

  Future<ImSession> createImGroup(ImSession imSession) async {
    final groupDao = ImGroupDao();
    var imSessionDao = ImSessionDao();

    ImGroup group = imSession.group!;
    await groupDao.batchInserts([group]);
    await imSessionDao.batchInserts([imSession]);
    return imSession;
  }

  Future<ImSession?> addToGroup(String userId, GroupAddReq groupAddReq) async {
    bool groupChange = false;
    bool sessionChange = false;

    final groupDao = ImGroupDao();
    final sessionDao = ImSessionDao();
    final groupUserDao = ImGroupUserDao();

    ImGroup? imGroup = await groupDao.getByCodeAndUserId(groupAddReq.groupId, userId);
    ImSession? imSession = await sessionDao.getByGroupCodeAndUserId(groupAddReq.groupId, userId);

    if (groupAddReq.groupDesc != null && groupAddReq.groupDesc != imGroup!.description) {
      imGroup.description = groupAddReq.groupDesc;
      groupChange = true;
    }

    if (groupAddReq.groupName != null && imGroup!.name != groupAddReq.groupName) {
      imGroup.name = groupAddReq.groupName;
      groupChange = true;

      imSession!.group = imGroup!;
      imSession!.name = groupAddReq.groupName;

      sessionChange = true;
    }

    if (groupChange) {
      imGroup!.updateTime = DateTime.now();
      await groupDao.batchUpdates([imGroup]);
    }

    if (sessionChange) {
      await sessionDao.batchUpdates(userId, [imSession!]);
    }
    Set<ImGroupUser> groupUsers = await _toGroupUsers(imGroup!.id!, groupAddReq.userIdSet!);

    Set<ImGroupUser>? dbGroupUserSet = imGroup!.groupUserList;
    Set<ImGroupUser> insertImGroupUserSet = groupUsers.where((gu)=>!dbGroupUserSet!.contains(gu)).toSet();
    imGroup.groupUserList = insertImGroupUserSet!.toSet();
    groupUserDao.batchInserts(imGroup.id!, imGroup.groupUserList!);

    imGroup = await groupDao.getByCodeAndUserId(imGroup.code, userId);
    imSession!.group= imGroup;
    return imSession;
  }

  Future<ImSession?> exitGroup(String userId, GroupExitReq groupExitReq) async {
    final groupDao = ImGroupDao();
    final groupUserDao = ImGroupUserDao();
    final sessionDao = ImSessionDao();
    ImGroup? imGroup = await groupDao.getByCodeAndUserId(groupExitReq.groupId, userId);
    var updateGroupUsers = Set<ImGroupUser>();
    for (var groupUser in imGroup!.groupUserList!) {
      for (var uid in groupExitReq.userIdSet) {
        if (groupUser.userId == uid) {
          groupUser.status = ImGroupUserStatus.disable;
          groupUser.updateTime = DateTime.now();
          updateGroupUsers.add(groupUser);
        }
      }
    }
    if (updateGroupUsers.length == imGroup.groupUserList!.length) {
      imGroup.status = ImGroupStatus.dissolution;
      imGroup.updateTime = DateTime.now();
      await groupDao.batchUpdates([imGroup]);
    }

    await groupUserDao.batchUpdates(updateGroupUsers);
    imGroup!.status = ImGroupStatus.disable;
    imGroup.updateTime = DateTime.now();
    groupDao.batchUpdates([imGroup]);

    ImSession? imSession = await sessionDao.getByGroupCodeAndUserId(imGroup.code, userId);
    imSession!.group = imGroup;
    return imSession;
  }

  Future<ImSession?> delGroup(String userId, GroupDelReq groupDelReq) async {
    final groupDao = ImGroupDao();
    final sessionDao = ImSessionDao();
    ImGroup? imGroup = await groupDao.getByCodeAndUserId(groupDelReq.groupId, userId);
    imGroup!.status = ImGroupStatus.dissolution;
    imGroup.updateTime = DateTime.now();
    groupDao.batchUpdates([imGroup]);

    ImSession? session = await sessionDao.getByGroupCodeAndUserId(imGroup.code, userId);
    session!.status = ImSessionStatus.disable;
    session.updateTime = DateTime.now();
    sessionDao.batchUpdates(userId, [session]);
    return session;
  }

  Future<ImGroup?> getByCodeAndUserId(String groupCode, String userId) async {
    final groupDao = ImGroupDao();
    var imGroup = await groupDao.getByCodeAndUserId(groupCode, userId);
    if(imGroup != null) {
      await groupDao.loadGroupUsers(imGroup!);
    }
    return imGroup;
  }

  Future<void> rpcCreateGroup(ImSession imSession) async {
    var imSessionDao = ImSessionDao();
    final groupDao = ImGroupDao();

    ImGroup group = imSession.group!;
    RpcGroupService rpcGroupService = RpcGroupService();

    var userIdList = group.groupUserList!.map((gu)=>gu.userId).toList();
    var groupCreateReq = new GroupCreateReq(groupName: group.name!, groupId: group.code, userIdSet: userIdList);
    groupCreateReq.groupDesc = group.description;
    OkResp okResp = await rpcGroupService.groupCreate(groupCreateReq);

    String sessionId = okResp.otherInfo!;
    group.updateTime = DateTime.now();
    if (group.status == ImGroupStatus.none) group.status = ImGroupStatus.enable;
    await groupDao.batchUpdates([group]);
    imSession.status = ImSessionStatus.enable;
    imSession.updateTime = group.updateTime;
    imSession.serverId = sessionId;
    await imSessionDao.batchUpdates(imSession.currentUserId!, [imSession]);
  }

  Future<ImGroup?> pullGroup(String userId, String groupCode) async {
    final groupDao = ImGroupDao();
    RpcGroupService rpcGroupService = RpcGroupService();
    var groupInfoReq = GroupInfoReq(groupId: groupCode);
    var groupInfoItem = await rpcGroupService.pullGroupInfo(groupInfoReq);
    await saveOrUpdates(userId, [groupInfoItem], true);
    return await groupDao.getByCodeAndUserId(groupCode, userId);
  }

  /// 创建群会话
  Future<void> _createSession(String userId, List<ImGroup> imGroups) async {
    List<ImSession> insertSessions = <ImSession>[];
    List<ImSession> updateSessions = <ImSession>[];

    final sessionDao = ImSessionDao();

    for (var group in imGroups) {
      var session = await sessionDao.getByGroupCodeAndUserId(group.code, userId);
      if (session == null) {
        session = ImSession.buildWithGroup(userId, group);
        insertSessions.add(session);
      } else {
        session.name = group.name;
        session.updateTime = DateTime.now();
        if (session.status == ImSessionStatus.created) {
          session.status = ImSessionStatus.enable;
        }
        updateSessions.add(session);
      }
    }
    await sessionDao.batchInserts(insertSessions);
    await sessionDao.batchUpdates(userId, updateSessions);
  }

  ImGroup _toImGroup(String userId, GroupInfoItem groupInfoItem) {
    var imGroup = ImGroup(code: groupInfoItem.groupId);
    imGroup.name = groupInfoItem.groupName;
    imGroup.currentUserId = userId;
    if (imGroup.name != null) { //  name为null, 表示imGroup未变更。groupUser有变化
      imGroup.ownerId = groupInfoItem.ownerId;
      imGroup.description = groupInfoItem.groupDesc;
      imGroup.status = ImGroupStatus.ofName(groupInfoItem.status?.name);
      imGroup.firstLetter = groupInfoItem.firstLetter;
    }

    var imGroupUsers = groupInfoItem.groupUserSet?.map((gu){return _toImGroupUser(gu);}).toSet();
    imGroup.groupUserList = imGroupUsers;
    return imGroup;
  }

  ImGroupUser _toImGroupUser(GroupUser groupUser) {
    var imGroupUser = ImGroupUser(groupUser.userId);
    imGroupUser.userName = groupUser.userName;
    imGroupUser.status = ImGroupUserStatus.ofName(groupUser.status?.name);
    imGroupUser.isShowName = groupUser.isShowName;
    imGroupUser.isDisturb = groupUser.isDisturb;
    imGroupUser.isTop = groupUser.isTop;
    imGroupUser.isWaking = groupUser.isWaking;
    return imGroupUser;
  }

  Future<Set<ImGroupUser>> _toGroupUsers(int groupId, List<String> userIdSet) async {
    Set<ImGroupUser> groupUsers = LinkedHashSet();
    if (userIdSet != null) {
      for(var u in userIdSet) {
        ImGroupUser groupUser = ImGroupUser(u);
        ImUser? imUser = await ImCacheFactory.getInstance().getImUser(u);
        groupUser.userName = imUser!.userName;
        groupUser.groupId = groupId;
        groupUser.status = ImGroupUserStatus.enable;
        groupUsers.add(groupUser);
      }
    }
    return groupUsers;
  }

  ImSession _buildSession(String userId, ImGroup group) {
    ImSession imSession = ImSession.buildWithGroup(userId, group);

    imSession.groupCode = group.code;
    imSession.name = group.name;
    return imSession;
  }

  ImGroupUser _toGroupUser(ImUser user) {
    ImGroupUser groupUser = ImGroupUser(user.userId);
    groupUser.userName = user.userName;
    groupUser.status = ImGroupUserStatus.enable;
    groupUser.user = user;
    return groupUser;
  }

  String _buildGroupName(List<ImUser> imUsers) {
    if (imUsers.length < 4) {
      return imUsers.map((u)=>u.userName).join(",");
    }
    List<ImUser> users = imUsers.sublist(0, 4);
    return users.map((u)=>u.userName).join(",");
  }
}
